/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "MovingClipMapTerrain.h"
#include <list>
#include "core/core.h"
#include "atir2vb.h"
#include "Texture.h"

namespace directx {

MovingClipMapTerrain::MovingClipMapTerrain(void)
:	m_pVertexBuffer(NULL),
	m_pDummy(NULL),
	m_pTexCoordBuffer(NULL),
	m_pDetailTexCoordBuffer(NULL),
	m_pIndexBuffer(NULL),
	m_pVertexDeclaration(NULL),
	m_pScreenDeclaration(NULL),
	m_pR2VBTarget(NULL),
	m_pGrassDeclaration(NULL),
	m_pGrassBuffer(NULL)
{
}

MovingClipMapTerrain::~MovingClipMapTerrain(void)
{
	release();
}

void MovingClipMapTerrain::draw()
{		
	const int X = 6;
	static sShaderLight lights[X];
	
	static std::list<ILight*>* lightList = coreGetScene()->getLights();
	static std::list<ILight*>::iterator it;
		
	int counter = 0;
	for ( it = lightList->begin(); it != lightList->end(); ++it ) {
		if (counter >= X) continue;
	//	if ((*it)->getRange() > 0 && (*it)->getRange() < (*it)->dist) continue;
		lights[counter].position = (*it)->getPosition();
		lights[counter].diffuseColor = (*it)->getDiffuseColor();
		lights[counter].ambientColor = (*it)->getAmbientColor();
		lights[counter].specularColor = (*it)->getSpecularColor();
		lights[counter].attenuation = (*it)->getRange();
		++counter;
	}

	//m_sMaterial.pEffect0->setData("lights", &lights[0], sizeof(lights));

	if (!m_pVertexBuffer) {
		reset();
	}
/*
	if (m_bUseR2VB) 
		drawR2VB(sShaderLight, counter);
	else */
		drawDefault(lights, counter);

}

void MovingClipMapTerrain::drawDefault(sShaderLight* lights, int numLights)
{	
	if (m_pTerrainShader) {
		//m_pTerrainShader->setTechnique("terrainDefault");
	
		// Bind the effect
		if ( m_pTerrainShader->bind() ) {
			
			if (m_pBlendMap)
				m_pTerrainShader->setTexture(hBlendMap, m_pBlendMap);

			if (m_pDetails1)
				m_pTerrainShader->setTexture(hDetailMap1, m_pDetails1);
			if (m_pDetails2)
				m_pTerrainShader->setTexture(hDetailMap2, m_pDetails2);
			if (m_pDetails3)
				m_pTerrainShader->setTexture(hDetailMap3, m_pDetails3);

			if (m_pHeightmap) {
				m_pTerrainShader->setTexture(hHeightMap, m_pHeightmap);

				m_pTerrainShader->setFloat(hTextureSize, &vTexSize.x, 2);
				m_pTerrainShader->setFloat(hOffset, &vOffset.x, 2);
				m_pTerrainShader->setFloat(hDetailOffset, &vDetailOffset.x, 2);
				m_pTerrainShader->setFloat(hInvOffsetScale, &fOffsetScale, 1);
				m_pTerrainShader->setFloat(hInvTess,  &vInvTess.x, 2);
				m_pTerrainShader->setFloat(hScaleFactor, &vScaleFactor.x, 3);
			}

			// Update the vertex declaration
			pd3dDevice->SetVertexDeclaration( m_pVertexDeclaration );

			// Render each effect pass
			for (int i=0; i<m_pTerrainShader->getNumPasses(); ++i) {
				if (m_pTerrainShader->beginPass(0)) {
					// Update the variables
					m_pTerrainShader->setVariables();
					// Draw the buffer
					drawBuffer();				
					m_pTerrainShader->endPass();
				}
				if (m_iNumGrassPatches > 0) {
					if (m_pTerrainShader->beginPass(1)) {
						m_pTerrainShader->setTexture(hGrassMap, m_pGrass);
						// Update the variables
						m_pTerrainShader->setVariables();
						drawGrass();
						m_pTerrainShader->endPass();
					}
				}
			}

			m_pTerrainShader->unbind();
		}
	}
}

static math::Vector2f vertices[] = 
{
	math::Vector2f(-1, -1),
	math::Vector2f( 1, -1),
	math::Vector2f( 1,  1),
	math::Vector2f(-1,  1)
};

void MovingClipMapTerrain::drawR2VB(math::Vector3 *lightPositions, math::Vector3 *lightColors, float *lightRanges)
{
	if (m_pTerrainShader) {
		m_pTerrainShader->setTechnique("terrainR2VB");
	
		// Bind the effect
		if ( m_pTerrainShader->bind() ) {
			int numPasses = m_pTerrainShader->getNumPasses();

			// Update the variables
			m_pTerrainShader->setVariables();
			//m_pTerrainShader->setFloat("lightPosition", &lightPositions[0].x, sizeof(lightPositions));
			//m_pTerrainShader->setFloat("lightColor", &lightColors[0].x, sizeof(lightColors));
			//m_pTerrainShader->setFloat("lightRange", &lightRanges[0], sizeof(lightRanges));
	
			if (m_pBlendMap)
				m_pTerrainShader->setTexture(hBlendMap, m_pBlendMap);

			if (m_pDetails1) 
				m_pTerrainShader->setTexture(hDetailMap1, m_pDetails1);
			if (m_pDetails2) 
				m_pTerrainShader->setTexture(hDetailMap1, m_pDetails2);
			if (m_pDetails3) 
				m_pTerrainShader->setTexture(hDetailMap1, m_pDetails3);

			if (m_pHeightmap) 
			{
				m_pTerrainShader->setTexture(hHeightMap, m_pHeightmap);

				m_pTerrainShader->setFloat(hTextureSize, &vTexSize.x, 2);
				m_pTerrainShader->setFloat(hOffset, &vOffset.x, 2);
				m_pTerrainShader->setFloat(hDetailOffset, &vDetailOffset.x, 2);
				m_pTerrainShader->setFloat(hInvOffsetScale, &fOffsetScale, 1);
				m_pTerrainShader->setFloat(hInvTess,  &vInvTess.x, 2);
				m_pTerrainShader->setFloat(hScaleFactor, &vScaleFactor.x, 3);
				float realTess = 2.0f/(m_uiTesselation-1);
				//m_pTerrainShader->setFloat("realTess", &realTess, 1);
			}

			// First pass: Render to the R2VB texture
	
			//m_pTerrainShader->setTexture("Tex", m_pHeightmap);
				
			pRenderer->bindRenderTarget(m_pR2VBTarget);
			//pd3dDevice->SetRenderTarget(0, m_pR2VBTarget);
			//pd3dDevice->SetRenderState(D3DRS_ZENABLE, 0);
			// The first pass is PassGeneration
			if ( m_pTerrainShader->beginPass(0) ) {
				m_pTerrainShader->setVariables();
				// Update the vertex declaration
				//pd3dDevice->SetVertexDeclaration( m_pScreenDeclaration );
				//pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, vertices, sizeof(math::Vector2f));
				drawBuffer();

				m_pTerrainShader->endPass();
			}
			pRenderer->unbindRenderTarget();
			m_pTerrainShader->unbind();

			//pd3dDevice->SetRenderState(D3DRS_ZENABLE, 1);

			HRESULT hr;
			// Enable the vertex buffer extension
			hr = pd3dDevice->SetRenderState( D3DRS_POINTSIZE, r2vbGlbEnable_Set(TRUE) );

			// Set the render target for the DMAP sampler
			hr = pd3dDevice->SetTexture( D3DDMAPSAMPLER, ((directx::Texture*)m_pR2VBTarget)->getD3DTexture() );
			//hr = pd3dDevice->SetTexture( D3DDMAPSAMPLER, d3drt );
			//Tell the driver that stream 1 is to be fetched from the DMAP texture
			hr = pd3dDevice->SetRenderState( D3DRS_POINTSIZE, r2vbVStrm2SmpMap_Set(1, R2VB_VSMP_OVR_DMAP) );
						
			m_pTerrainShader->bind();
			// Second Pass:
			if ( m_pTerrainShader->beginPass(1) ) {
				// Render the quad on screen
				pd3dDevice->SetStreamSource( 0, m_pVertexBuffer, 0, sizeof( math::Vector3 ) );

				hr = pd3dDevice->SetStreamSource( 1, m_pVertexBuffer, 0, sizeof( float ) );
				pd3dDevice->SetStreamSource( 2, m_pTexCoordBuffer, 0, sizeof( math::Vector2f ));
				//pd3dDevice->SetStreamSource( 3, m_pDetailTexCoordBuffer, 0, sizeof( math::Vector2f ) );

				// Update the vertex declaration
				pd3dDevice->SetVertexDeclaration( m_pVertexDeclaration );

				m_pTerrainShader->setVariables();

				//set indices
				pd3dDevice->SetIndices(m_pIndexBuffer);
				//draw triangles
				pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_uiNumVertices, 0, 2*(m_uiTesselation-1)*(m_uiTesselation-1));

				m_pTerrainShader->endPass();
			}
			m_pTerrainShader->unbind();

			// Reset the stream to regular vertex buffer mode
			pd3dDevice->SetRenderState( D3DRS_POINTSIZE, r2vbVStrm2SmpMap_Set(1, R2VB_VSMP_OVR_DIS) );
			// Unbind the DMAP texture
			pd3dDevice->SetTexture(D3DDMAPSAMPLER, NULL);
			// Disable the vertex buffer extension
			pd3dDevice->SetRenderState( D3DRS_POINTSIZE, r2vbGlbEnable_Set(FALSE) );

		}
	}

	m_pR2VBTarget->display(10, 10, 10);

}

void MovingClipMapTerrain::reset()
{
	// We have a current ATI graphics card. ATI doesn't know Vertex texture fetch
	// A workaround is using "Render to Vertex Buffer"
	if (m_bUseR2VB) {
		// Create a special render target for the R2VB extension. The data will be used for height values
		m_pR2VBTarget = ITexture::CreateRenderTarget(m_uiTesselation, m_uiTesselation, R32F);
		/*
		D3DSURFACE_DESC desc;
		pd3dDevice->CreateTexture(m_uiTesselation, m_uiTesselation, 1, D3DUSAGE_RENDERTARGET | D3DUSAGE_DMAP, D3DFMT_R32F, D3DPOOL_DEFAULT, &d3drt, NULL);

		// Grab the format info so that we can recreate it in case we need to reset the device
		((LPDIRECT3DTEXTURE9) d3drt)->GetLevelDesc(0, &desc);

		((LPDIRECT3DTEXTURE9) d3drt)->GetSurfaceLevel(0, &m_pR2VBTarget);
		*/

		D3DVERTEXELEMENT9 SCREEN_DECL[] = {
			{ 0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  // pos
			D3DDECL_END()
		};

		if (FAILED( pd3dDevice->CreateVertexDeclaration( SCREEN_DECL, &m_pScreenDeclaration))) 
		{
			coreGetLog()->WriteError("ERror creating vertex declaration");
		}
		pd3dDevice->CreateVertexBuffer( 1, 
									D3DUSAGE_WRITEONLY,
									0, 
									D3DPOOL_DEFAULT, 
									&m_pDummy, 
									NULL );
	}

	// Raw copied vertex data
	void *pData;

	pd3dDevice->CreateVertexBuffer( m_uiNumVertices*sizeof(math::Vector3), 
									D3DUSAGE_WRITEONLY,
									0, 
									D3DPOOL_DEFAULT, 
									&m_pVertexBuffer, 
									NULL );
	pd3dDevice->CreateVertexBuffer( m_uiNumVertices*sizeof(math::Vector2f), 
									D3DUSAGE_WRITEONLY,
									0, 									
									D3DPOOL_DEFAULT, 
									&m_pTexCoordBuffer, 
									NULL );

	pd3dDevice->CreateVertexBuffer( m_uiNumVertices*sizeof(math::Vector2f), 
									D3DUSAGE_WRITEONLY,
									0, 
									D3DPOOL_DEFAULT, 
									&m_pDetailTexCoordBuffer, 
									NULL );

	pd3dDevice->CreateIndexBuffer( sizeof(WORD)*m_uiNumVertices*6, 
									D3DUSAGE_WRITEONLY, 
									D3DFMT_INDEX16, 
									D3DPOOL_DEFAULT, 
									&m_pIndexBuffer, 
									NULL);

	// Create the vertex buffer pointer that points to the data in m_pVertices and allocate memory for pVertices
	m_pVertexBuffer->Lock( 0, sizeof(math::Vector3)*m_uiNumVertices, (void**)&pData, 0 );
	memcpy(pData, m_pVertices, sizeof(math::Vector3)*m_uiNumVertices );
	m_pVertexBuffer->Unlock();
	
	m_pTexCoordBuffer->Lock( 0, sizeof(math::Vector2f)*m_uiNumVertices, (void**)&pData, 0 );
	memcpy(pData, m_pTexCoords, sizeof(math::Vector2f)*m_uiNumVertices );
	m_pTexCoordBuffer->Unlock();

	m_pDetailTexCoordBuffer->Lock( 0, sizeof(math::Vector2f)*m_uiNumVertices, (void**)&pData, 0 );
	memcpy(pData, m_pDetailTexCoords, sizeof(math::Vector2f)*m_uiNumVertices );
	m_pDetailTexCoordBuffer->Unlock();

	m_pIndexBuffer->Lock( 0, m_uiNumVertices*6,(void**)&pData, 0);   //lock buffer
	memcpy(pData, m_pIndices, sizeof(short)*m_uiNumVertices*6);     //copy data
	m_pIndexBuffer->Unlock();                                       //unlock buffer

	D3DVERTEXELEMENT9 VB_DECLARATION[] = 
	{
		{ 0, 0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  // pos
		{ 1, 0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },  // normal
		{ 2, 0, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },  // texCoords
		{ 3, 0, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2 },  // DetailCoords
		{ 4, 0, D3DDECLTYPE_FLOAT1,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },  // Displacement
		D3DDECL_END()
	};

	if (FAILED( pd3dDevice->CreateVertexDeclaration( VB_DECLARATION, &m_pVertexDeclaration))) 
	{
		coreGetLog()->WriteError("ERror creating vertex declaration");
	}
}

void MovingClipMapTerrain::release()
{
	RELEASE(m_pVertexBuffer);
	RELEASE(m_pDummy);
	RELEASE(m_pTexCoordBuffer);
	RELEASE(m_pDetailTexCoordBuffer);
	RELEASE(m_pIndexBuffer);
	RELEASE(m_pVertexDeclaration);
	RELEASE(m_pScreenDeclaration);
	RELEASE(m_pGrassDeclaration);
	RELEASE(m_pGrassBuffer);
}

void MovingClipMapTerrain::drawBuffer()
{
	pd3dDevice->SetVertexDeclaration( m_pVertexDeclaration);
	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, m_pVertexBuffer, 0, sizeof( math::Vector3 ) );
	/// 1 is Normal
	pd3dDevice->SetStreamSource( 2, m_pTexCoordBuffer, 0, sizeof( math::Vector2f ) );
	pd3dDevice->SetStreamSource( 3, m_pDetailTexCoordBuffer, 0, sizeof( math::Vector2f ) );
	pd3dDevice->SetStreamSource( 4, NULL, 0, 0 );

	//set indices
	pd3dDevice->SetIndices(m_pIndexBuffer);

	//draw triangles
	pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_uiNumVertices, 0, 2*(m_uiTesselation-1)*(m_uiTesselation-1));
}


void MovingClipMapTerrain::createGrassPatches(int numPatches, ITexture *grass)
{
	terrain::GrassPatch *pData;

	RELEASE(m_pGrassDeclaration);
	RELEASE(m_pGrassBuffer);
	// Create the vertex declaration and buffer

	m_pGrass = grass;
	m_iNumGrassPatches = numPatches;

	D3DVERTEXELEMENT9 GRASS_DECLARATION[] = 
	{
		{ 0, 0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  // pos
		{ 0, 12, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_PSIZE,    0},	// Size
		D3DDECL_END()
	};

	if (FAILED( pd3dDevice->CreateVertexDeclaration( GRASS_DECLARATION, &m_pGrassDeclaration))) 
	{
		coreGetLog()->WriteError("Error creating vertex declaration");
	}

	pd3dDevice->CreateVertexBuffer( m_iNumGrassPatches*sizeof(terrain::GrassPatch), 
									D3DUSAGE_WRITEONLY,
									0, 
									D3DPOOL_DEFAULT, 
									&m_pGrassBuffer, 
									NULL );
	m_pGrassBuffer->Lock( 0, sizeof(terrain::GrassPatch)*m_iNumGrassPatches, (void**)&pData, 0 );

	// GEt the size of the diffuse tex
	float txsize = m_pBlendMap->getWidth() * vScaleFactor.x;
	float tysize = m_pBlendMap->getHeight() * vScaleFactor.z;

	int count = 0;
	float rx, ry;
//	unsigned char pixel[3];
	float height;
	math::Vector3 norm;
	// Generate new grass spots
	while (count < numPatches) {	
		rx = (float)(rand() % (int)txsize);
		ry = (float)(rand() % (int)tysize);
		getElevationAndNormal(rx-512, ry-512, height, norm);
		//m_pDiffuse->getPixel(rx/vScaleFactor.x, ry/vScaleFactor.z);
		pData[count].position = math::Vector3(rx-512, height, ry-512);
		pData[count].size = 100;
		++count;
	}

	m_pGrassBuffer->Unlock();
}


void MovingClipMapTerrain::drawGrass() 
{
	pd3dDevice->SetVertexDeclaration( m_pGrassDeclaration );
	pd3dDevice->SetStreamSource( 0, m_pGrassBuffer, 0, sizeof(terrain::GrassPatch) );
	pd3dDevice->DrawPrimitive(D3DPT_POINTLIST, 0, m_iNumGrassPatches);
}

} // namespace
